
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sched.h>
#include <string.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <signal.h>

#define _GUN_SOURCE
#include <sys/types.h>
#include <sys/socket.h>

#include "openssl/ssl.h"
#include "openssl/err.h"

#define CERT_PATH "cert"
#define CERT_FILE  CERT_PATH"/tls_server.pem"
#define KEY_FILE CERT_PATH"/tls_server.key"
#define CA_FILE CERT_PATH"/root.pem"

typedef struct sockaddr_in SockAddr;

SSL_CTX *g_ssl_ctx = NULL;

static SSL_CTX *InitSslCtx()
{
    SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
    SSL_CTX_set_security_level(ctx, 0);
    SSL_CTX_use_certificate_file(ctx, CERT_FILE, SSL_FILETYPE_PEM);
    SSL_CTX_use_PrivateKey_file(ctx, KEY_FILE, SSL_FILETYPE_PEM);
    SSL_CTX_check_private_key(ctx);
    SSL_CTX_load_verify_locations(ctx, CA_FILE, NULL);
    ERR_print_errors_fp(stdout);
    return ctx;
}

static SSL *TlsAccept(SSL_CTX *ctx, int fd)
{
    SSL *ssl = SSL_new(ctx);
    SSL_set_fd(ssl, fd);

    int ret = 0;
    int err = SSL_ERROR_WANT_READ;
    while (ret <= 0 && (err == SSL_ERROR_WANT_READ || SSL_ERROR_WANT_WRITE)) {
        ret = SSL_accept(ssl);
        if (ret == 1) {
            return ssl;
        }
        err = SSL_get_error(ssl, ret);
    }

    ERR_print_errors_fp(stdout);
    SSL_free(ssl);
    return NULL;
}

static int TcpConnect(const unsigned char *ip, int port, int timeout)
{
    int fd = -1;
    struct timeval tv = { 0 };
    SockAddr addr = { 0 };

    int ret = inet_pton(AF_INET, ip, &(addr.sin_addr));
    if (ret != 1) {
        return -1;
    }
    addr.sin_port = htons(port);;
    addr.sin_family = AF_INET;

    fd = socket(addr.sin_family, SOCK_STREAM, 0);

    if (timeout > 0) {
        tv.tv_sec = timeout;
        setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    }

    ret = connect(fd, (const struct sockaddr *)&addr, sizeof(addr));
    if (ret != 0) {
        printf("conenct filed, errno = %d\n", errno);
        return -1;
    }
    return fd;
}

static int TcpListen(const unsigned char *ip, int port)
{
    errno = 0;
    int fd = -1;
    SockAddr addr = { 0 };

    int ret = inet_pton(AF_INET, ip, &(addr.sin_addr));
    if (ret != 1) {
        return -1;
    }
    addr.sin_port = htons(port);
    addr.sin_family = AF_INET;

    fd = socket(addr.sin_family, SOCK_STREAM, 0);

    int reuse = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

    ret = bind(fd, (const struct sockaddr *)&addr, sizeof(addr));
    if (ret != 0) {
        printf("bind filed, errno = %d\n", errno);
        return -1;
    }

    ret = listen(fd, 0);
    if (ret != 0) {
        printf("listen filed, errno = %d\n", errno);
        return -1;
    }
    return fd;
}

static int TcpAccept(int listenFd, int timeout)
{
    SockAddr addr = { 0 };
    socklen_t addrLen = sizeof(addr);
    // int fd = accept4(listenFd, (struct sockaddr*)&addr, &addrLen, SOCK_NONBLOCK);
    int fd = accept(listenFd, (struct sockaddr*)&addr, &addrLen);
    if (fd == -1) {
        printf("accept4 filed, errno = %d\n", errno);
        return -1;
    }

    if (timeout > 0) {
        struct timeval tv = { 0 };
        tv.tv_sec = timeout;
        setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    }
    return fd;
}

static pthread_mutex_t g_fd_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t g_count_lock = PTHREAD_MUTEX_INITIALIZER;

const unsigned char * g_real_ocsp_server_ip = "127.0.0.1";
int g_real_ocsp_server_port = 8888;

const unsigned char *g_ocsp_proxy_ip = "127.0.0.1";
int g_ocsp_proxy_port = 9999;

int g_thread_count = 0;

static void *work(void *args)
{
    int ret;
    pthread_detach(pthread_self());
    signal(SIGPIPE, SIG_IGN);
    int fd = *(int *)args;
    pthread_mutex_unlock(&g_fd_lock);

    SSL *ssl = TlsAccept(g_ssl_ctx, fd);
    if (ssl == NULL) {
        return NULL;
    }

    pthread_mutex_lock(&g_count_lock);
    g_thread_count++;
    pthread_mutex_unlock(&g_count_lock);

    #define BUF_LEN 10240
    uint8_t buf[BUF_LEN];
    int ocsp_fd = TcpConnect(g_real_ocsp_server_ip, g_real_ocsp_server_port, 1);
    if (ocsp_fd < 0) {
        goto end;
    }

    int retry_count = 3;
    int len = 0;
    while (retry_count--) {
        ret = SSL_read(ssl, buf + len, BUF_LEN - len);
        if (ret > 0) {
            len += ret;
        }
        usleep(100000);
    }

    printf("read from ocsp client msg len = %d\n", len);

    ret = write(ocsp_fd, buf, len);
    if (ret != len) {
        printf("write to ocsp server error, errno = %d\n", errno);
        goto end;
    }
    printf("write to ocsp server msg len = %d\n", len);

    retry_count = 3;
    len = 0;
    while (retry_count--) {
        ret = read(ocsp_fd, buf + len, BUF_LEN - len);
        if (ret > 0) {
            len += ret;
        }
        usleep(100000);
    }

    printf("read from ocsp server msg len = %d\n", len);

    ret = SSL_write(ssl, buf, len);
    if (ret != len) {
        printf("write to ocsp client error, errno = %d\n", len);
    }
    printf("write to ocsp client msg len = %d\n", len);

end:
    ERR_print_errors_fp(stdout);
    close(ocsp_fd);
    SSL_shutdown(ssl);
    SSL_free(ssl);
    close(fd);
    pthread_mutex_lock(&g_count_lock);
    g_thread_count--;
    pthread_mutex_unlock(&g_count_lock);
    return NULL;
}

int main()
{
    int ret;
    signal(SIGPIPE, SIG_IGN);
    g_ssl_ctx = InitSslCtx();
    if (g_ssl_ctx == NULL) {
        return 0;
    }

    BIO *log = NULL;
#ifndef OPENSSL_NO_SSL_TRACE
    log = BIO_new_fp(stdout, BIO_CLOSE);
    if (log != NULL) {
        SSL_CTX_set_msg_callback(g_ssl_ctx, SSL_trace);
        SSL_CTX_set_msg_callback_arg(g_ssl_ctx, log);
    }
#endif

    int listenFd = TcpListen(g_ocsp_proxy_ip, g_ocsp_proxy_port);
    if (listenFd < 0) {
        goto end;
    }

    int count = 1000;
    int fd;
    while (count--) {
        pthread_mutex_lock(&g_fd_lock);
        fd =  TcpAccept(listenFd, 1);
        if (fd == -1) {
            printf("TcpAccept filed, errno = %d\n", errno);
            goto end;
        }

        pthread_t tid;
        ret = pthread_create(&tid, NULL, work, &fd);
        if (ret != 0) {
            printf("pthread_create filed, ret = %d\n", ret);
        }
    }

    while (1) {
        if (g_thread_count == 0) {
            break;
        }
        sleep(1);
    }

end:
    BIO_free(log);
    SSL_CTX_free(g_ssl_ctx);
    return 0;
}
